﻿using System;
using System.Linq;
using CoreEx.Common.Data;
using CoreEx.Database.Metadata;
using CoreEx.MicroMapper;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using CoreEx.DynamicInvoker;
namespace CoreEx.Common.Repository.Implementation
{    
    /// <summary>
    /// Maps <see cref="IRow"/> instances into <typeparamref name="T"/> instances.    
    /// </summary>
    /// <typeparam name="T">The target <see cref="Type"/></typeparam>
    /// <remarks>
    /// This mapper will only map non-relational properties. 
    /// </remarks>    
    public class ObjectMapper<T> : MicroMapper<IRow,T>, IObjectMapper<T>
    {
        private readonly ITypeMappingInfo _typeMappingInfo;

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectMapper{T}"/> class.
        /// </summary>
        /// <param name="typeMappingInfo">The <see cref="ITypeMappingInfo"/> 
        /// that describes the relationship between a <see cref="Type"/> and an <see cref="ITable"/>.</param>
        public ObjectMapper(ITypeMappingInfo typeMappingInfo)
        {
            _typeMappingInfo = typeMappingInfo;
        }
        
        public override void Initialize(IServiceContainer source)
        {
            base.Initialize(source);

            //Create a propertyinfo dictionary indexed by the property name
            var properties = _typeMappingInfo.ColumnMappings.Select(c => c.Value.Property).ToDictionary(p => p.Name);

            //If the target type is an interface, leave it up to the servicecontainer to provide a new instance.
            if (typeof(T).IsInterface)
                TargetFactory = row => source.GetService<T>();

            //Set up the source accessor list
            SourceAccessorSelector = row => _typeMappingInfo.ColumnMappings.Where(c => c.Value.RelationType == CardinalityType.None)
                .Select(c =>
                (IAccessorInfo)
                new AccessorInfo {Name = c.Value.Property.Name, DataType = c.Value.ColumnInfo.DataType});

            //Set up the target accessor list
            TargetAccessorSelector = target => _typeMappingInfo.ColumnMappings.Where(c => c.Value.RelationType == CardinalityType.None)
                .Select(c =>
                (IAccessorInfo)
                new AccessorInfo { Name = c.Value.Property.Name, DataType = c.Value.Property.PropertyType});

            //Extract the column value based on the typemappinginfo          
            SourceAccessor = (accessorInfo, row) => row[_typeMappingInfo.ColumnMappings[properties[accessorInfo.Name]].ColumnInfo.ColumnName];        

            //Apply the extracted valued to the target property 
            TargetAccessor = (accessorInfo, target, value) => properties[accessorInfo.Name].DynamicInvoke(target, value);                            
        }
    }
}
